Skip to content

feat(tree): Allow event deferral during transactions#27238

Open
Josmithr wants to merge 13 commits intomicrosoft:mainfrom
Josmithr:tree/allow-event-buffering-in-transactions
Open

feat(tree): Allow event deferral during transactions#27238
Josmithr wants to merge 13 commits intomicrosoft:mainfrom
Josmithr:tree/allow-event-buffering-in-transactions

Conversation

@Josmithr
Copy link
Copy Markdown
Contributor

@Josmithr Josmithr commented May 6, 2026

Adds a deferEvents flag to runTransaction, which allows users to opt-into deffering events until after the transaction has completed.

@Josmithr Josmithr requested a review from noencke May 6, 2026 00:10
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 6, 2026

Hi! Thank you for opening this PR. Want me to review it?

Based on the diff (479 lines, 12 files), I've queued these reviewers:

  • Correctness — logic errors, race conditions, lifecycle issues
  • Security — vulnerabilities, secret exposure, injection
  • API Compatibility — breaking changes, release tags, type design
  • Performance — algorithmic regressions, memory leaks
  • Testing — coverage gaps, hollow tests

How this works

  • Adjust the reviewer set by ticking/unticking boxes above. Reviewer toggles alone don't trigger anything.

  • Tick Start review below to dispatch the review fleet.

  • After review finishes, tick Start review again to request another run — it auto-resets after each dispatch.

  • This comment updates as new commits land; your reviewer selections are preserved.

  • Start review

@Josmithr Josmithr marked this pull request as ready for review May 6, 2026 23:27
@Josmithr Josmithr requested a review from a team as a code owner May 6, 2026 23:27
Copilot AI review requested due to automatic review settings May 6, 2026 23:27
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds opt-in buffering of SimpleTree change events during synchronous runTransaction calls so that listeners observe a single coalesced notification after commit, and no notifications for synchronous rollbacks.

Changes:

  • Add deferEvents?: boolean to RunTransactionParams and wrap TreeCheckout.runTransaction with withBufferedTreeEvents when enabled.
  • Extend withBufferedTreeEvents/kernel event buffering to support discarding buffered events (used on rollback).
  • Add new SharedTreeView tests validating buffered/coalesced nodeChanged/treeChanged behavior, including nested transaction scenarios.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 4 comments.

File Description
packages/dds/tree/src/test/shared-tree/treeCheckout.spec.ts Adds coverage for deferred/coalesced nodeChanged/treeChanged behavior and rollback semantics.
packages/dds/tree/src/simple-tree/core/treeNodeKernel.ts Adds discard support to the kernel’s event buffering so buffered events can be dropped instead of flushed.
packages/dds/tree/src/simple-tree/api/transactionTypes.ts Extends the public transaction params API with the new deferEvents option and its documentation.
packages/dds/tree/src/shared-tree/treeCheckout.ts Implements deferEvents support by buffering events around the transaction and discarding on synchronous rollback.

Comment thread packages/dds/tree/src/simple-tree/api/transactionTypes.ts
Comment thread packages/dds/tree/src/simple-tree/api/transactionTypes.ts Outdated
Comment thread packages/dds/tree/src/test/shared-tree/treeCheckout.spec.ts Outdated
Comment thread packages/dds/tree/src/test/shared-tree/treeCheckout.spec.ts
@Josmithr Josmithr changed the title feat(tree): Allow event buffering during transactions feat(tree): Allow event deferral during transactions May 6, 2026
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 7, 2026

🔗 No broken links found! ✅

Your attention to detail is admirable.

linkcheck output


> fluid-framework-docs-site@0.0.0 ci:check-links /home/runner/work/FluidFramework/FluidFramework/docs
> start-server-and-test "npm run serve -- --no-open" 3000 check-links

1: starting server using command "npm run serve -- --no-open"
and when url "[ 'http://127.0.0.1:3000' ]" is responding with HTTP status code 200
running tests using command "npm run check-links"


> fluid-framework-docs-site@0.0.0 serve
> docusaurus serve --no-open

[SUCCESS] Serving "build" directory at: http://localhost:3000/

> fluid-framework-docs-site@0.0.0 check-links
> linkcheck http://localhost:3000 --skip-file skipped-urls.txt

Crawling...

Stats:
  288641 links
    1922 destination URLs
    2172 URLs ignored
       0 warnings
       0 errors


* @param options - Optional configuration. See {@link WithBufferedTreeEventsOptions}.
*/
export function withBufferedTreeEvents(callback: () => void): void {
export function withBufferedTreeEvents<TResult>(
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it might be cleaner to have the primary callback passed to withBufferedTreeEvents return true/false to determine discard behavior, rather than having this additional parameter. It would make sense since the whole point of this method is to buffer events, and the return value of the main callback would be controlling whether that happens or not.

The downside is that then you couldn't return the return value of the transaction directly, so you might have to add a few extra lines at the call site to capture the return value within the callback and have it outside. But the call site is already split up anyway to accommodate the current scheme:

const result = callback();
discard = options?.shouldDiscard?.(result) === true;
return result;

It's not like it's a super clean one liner, so I don't think it'd make it too much messier. And then this whole interface would get much simpler and self-describing. What do you think?

@@ -542,6 +582,22 @@ class KernelEventBuffer implements Listenable<KernelEvents> {
}
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can this method delegate to discard() now?

It clears childrenChangeBuffer, fieldMarksBuffer, and invalidatedFieldMarkKeys - sometimes.
It also clears subTreeChangedBuffer - sometimes.

But it appears to me that the "sometimes" is purely an optimization - i.e., the only time it doesn't clear them is because they are already clear. If that is true, then I think the cleanliness of removing all those clears and simply calling discard() at the end of flush() is worth it. I imagine that clear() on an empty map/set in JS has negligible perf cost.

}

// @alpha @input
export interface RunTransactionAsyncParams extends RunTransactionParams {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMO, no reason to add this until we need it. runTransactionAsync can just take the more general RunTransactionParams for now. We can always add a type like this later if necessary.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That was how I originally had it, but it seemed a bit weird to me for runTransaction to take RunTransactionSyncParams, while runTransactionAsync takes just RunTransactionParams.

But maybe it would make more sense to just do some type renames (these are alpha APIs after all).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants